1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.testing;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.annotations.Beta;
23 import com.google.common.base.Converter;
24 import com.google.common.base.Objects;
25 import com.google.common.collect.ClassToInstanceMap;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Lists;
28 import com.google.common.collect.Maps;
29 import com.google.common.collect.MutableClassToInstanceMap;
30 import com.google.common.reflect.Invokable;
31 import com.google.common.reflect.Parameter;
32 import com.google.common.reflect.Reflection;
33 import com.google.common.reflect.TypeToken;
34
35 import junit.framework.Assert;
36 import junit.framework.AssertionFailedError;
37
38 import java.lang.reflect.Constructor;
39 import java.lang.reflect.InvocationTargetException;
40 import java.lang.reflect.Member;
41 import java.lang.reflect.Method;
42 import java.lang.reflect.Modifier;
43 import java.lang.reflect.ParameterizedType;
44 import java.lang.reflect.Type;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.concurrent.ConcurrentMap;
48
49 import javax.annotation.Nullable;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @Beta
69 public final class NullPointerTester {
70
71 private final ClassToInstanceMap<Object> defaults =
72 MutableClassToInstanceMap.create();
73 private final List<Member> ignoredMembers = Lists.newArrayList();
74
75 private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE;
76
77
78
79
80
81 public <T> NullPointerTester setDefault(Class<T> type, T value) {
82 defaults.putInstance(type, checkNotNull(value));
83 return this;
84 }
85
86
87
88
89
90
91 public NullPointerTester ignore(Method method) {
92 ignoredMembers.add(checkNotNull(method));
93 return this;
94 }
95
96
97
98
99
100 public void testConstructors(Class<?> c, Visibility minimalVisibility) {
101 for (Constructor<?> constructor : c.getDeclaredConstructors()) {
102 if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) {
103 testConstructor(constructor);
104 }
105 }
106 }
107
108
109
110
111
112 public void testAllPublicConstructors(Class<?> c) {
113 testConstructors(c, Visibility.PUBLIC);
114 }
115
116
117
118
119
120
121 public void testStaticMethods(Class<?> c, Visibility minimalVisibility) {
122 for (Method method : minimalVisibility.getStaticMethods(c)) {
123 if (!isIgnored(method)) {
124 testMethod(null, method);
125 }
126 }
127 }
128
129
130
131
132
133 public void testAllPublicStaticMethods(Class<?> c) {
134 testStaticMethods(c, Visibility.PUBLIC);
135 }
136
137
138
139
140
141
142 public void testInstanceMethods(Object instance, Visibility minimalVisibility) {
143 for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) {
144 testMethod(instance, method);
145 }
146 }
147
148 ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) {
149 ImmutableList.Builder<Method> builder = ImmutableList.builder();
150 for (Method method : minimalVisibility.getInstanceMethods(c)) {
151 if (!isIgnored(method)) {
152 builder.add(method);
153 }
154 }
155 return builder.build();
156 }
157
158
159
160
161
162
163 public void testAllPublicInstanceMethods(Object instance) {
164 testInstanceMethods(instance, Visibility.PUBLIC);
165 }
166
167
168
169
170
171
172
173
174
175 public void testMethod(@Nullable Object instance, Method method) {
176 Class<?>[] types = method.getParameterTypes();
177 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
178 testMethodParameter(instance, method, nullIndex);
179 }
180 }
181
182
183
184
185
186
187 public void testConstructor(Constructor<?> ctor) {
188 Class<?> declaringClass = ctor.getDeclaringClass();
189 checkArgument(Modifier.isStatic(declaringClass.getModifiers())
190 || declaringClass.getEnclosingClass() == null,
191 "Cannot test constructor of non-static inner class: %s", declaringClass.getName());
192 Class<?>[] types = ctor.getParameterTypes();
193 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
194 testConstructorParameter(ctor, nullIndex);
195 }
196 }
197
198
199
200
201
202
203
204
205
206
207 public void testMethodParameter(
208 @Nullable final Object instance, final Method method, int paramIndex) {
209 method.setAccessible(true);
210 testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass());
211 }
212
213
214
215
216
217
218
219 public void testConstructorParameter(Constructor<?> ctor, int paramIndex) {
220 ctor.setAccessible(true);
221 testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass());
222 }
223
224
225 public enum Visibility {
226
227 PACKAGE {
228 @Override boolean isVisible(int modifiers) {
229 return !Modifier.isPrivate(modifiers);
230 }
231 },
232
233 PROTECTED {
234 @Override boolean isVisible(int modifiers) {
235 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
236 }
237 },
238
239 PUBLIC {
240 @Override boolean isVisible(int modifiers) {
241 return Modifier.isPublic(modifiers);
242 }
243 };
244
245 abstract boolean isVisible(int modifiers);
246
247
248
249
250
251 final boolean isVisible(Member member) {
252 return isVisible(member.getModifiers());
253 }
254
255 final Iterable<Method> getStaticMethods(Class<?> cls) {
256 ImmutableList.Builder<Method> builder = ImmutableList.builder();
257 for (Method method : getVisibleMethods(cls)) {
258 if (Invokable.from(method).isStatic()) {
259 builder.add(method);
260 }
261 }
262 return builder.build();
263 }
264
265 final Iterable<Method> getInstanceMethods(Class<?> cls) {
266 ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap();
267 for (Method method : getVisibleMethods(cls)) {
268 if (!Invokable.from(method).isStatic()) {
269 map.putIfAbsent(new Signature(method), method);
270 }
271 }
272 return map.values();
273 }
274
275 private ImmutableList<Method> getVisibleMethods(Class<?> cls) {
276
277
278 String visiblePackage = Reflection.getPackageName(cls);
279 ImmutableList.Builder<Method> builder = ImmutableList.builder();
280 for (Class<?> type : TypeToken.of(cls).getTypes().classes().rawTypes()) {
281 if (!Reflection.getPackageName(type).equals(visiblePackage)) {
282 break;
283 }
284 for (Method method : type.getDeclaredMethods()) {
285 if (!method.isSynthetic() && isVisible(method)) {
286 builder.add(method);
287 }
288 }
289 }
290 return builder.build();
291 }
292 }
293
294
295 private static final class Signature {
296 private final String name;
297 private final ImmutableList<Class<?>> parameterTypes;
298
299 Signature(Method method) {
300 this(method.getName(), ImmutableList.copyOf(method.getParameterTypes()));
301 }
302
303 Signature(String name, ImmutableList<Class<?>> parameterTypes) {
304 this.name = name;
305 this.parameterTypes = parameterTypes;
306 }
307
308 @Override public boolean equals(Object obj) {
309 if (obj instanceof Signature) {
310 Signature that = (Signature) obj;
311 return name.equals(that.name)
312 && parameterTypes.equals(that.parameterTypes);
313 }
314 return false;
315 }
316
317 @Override public int hashCode() {
318 return Objects.hashCode(name, parameterTypes);
319 }
320 }
321
322
323
324
325
326
327
328
329
330
331 private void testParameter(Object instance, Invokable<?, ?> invokable,
332 int paramIndex, Class<?> testedClass) {
333 if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) {
334 return;
335 }
336 Object[] params = buildParamList(invokable, paramIndex);
337 try {
338 @SuppressWarnings("unchecked")
339 Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable;
340 unsafe.invoke(instance, params);
341 Assert.fail("No exception thrown for parameter at index " + paramIndex
342 + " from " + invokable + Arrays.toString(params) + " for " + testedClass);
343 } catch (InvocationTargetException e) {
344 Throwable cause = e.getCause();
345 if (policy.isExpectedType(cause)) {
346 return;
347 }
348 AssertionFailedError error = new AssertionFailedError(
349 "wrong exception thrown from " + invokable + ": " + cause);
350 error.initCause(cause);
351 throw error;
352 } catch (IllegalAccessException e) {
353 throw new RuntimeException(e);
354 }
355 }
356
357 private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) {
358 ImmutableList<Parameter> params = invokable.getParameters();
359 Object[] args = new Object[params.size()];
360
361 for (int i = 0; i < args.length; i++) {
362 Parameter param = params.get(i);
363 if (i != indexOfParamToSetToNull) {
364 args[i] = getDefaultValue(param.getType());
365 Assert.assertTrue(
366 "Can't find or create a sample instance for type '"
367 + param.getType()
368 + "'; please provide one using NullPointerTester.setDefault()",
369 args[i] != null || isNullable(param));
370 }
371 }
372 return args;
373 }
374
375 private <T> T getDefaultValue(TypeToken<T> type) {
376
377
378 @SuppressWarnings("unchecked")
379 T defaultValue = (T) defaults.getInstance(type.getRawType());
380 if (defaultValue != null) {
381 return defaultValue;
382 }
383 @SuppressWarnings("unchecked")
384 T arbitrary = (T) ArbitraryInstances.get(type.getRawType());
385 if (arbitrary != null) {
386 return arbitrary;
387 }
388 if (type.getRawType() == Class.class) {
389
390 @SuppressWarnings("unchecked")
391 T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType();
392 return defaultClass;
393 }
394 if (type.getRawType() == TypeToken.class) {
395
396 @SuppressWarnings("unchecked")
397 T defaultType = (T) getFirstTypeParameter(type.getType());
398 return defaultType;
399 }
400 if (type.getRawType() == Converter.class) {
401 TypeToken<?> convertFromType = type.resolveType(
402 Converter.class.getTypeParameters()[0]);
403 TypeToken<?> convertToType = type.resolveType(
404 Converter.class.getTypeParameters()[1]);
405 @SuppressWarnings("unchecked")
406 T defaultConverter = (T) defaultConverter(convertFromType, convertToType);
407 return defaultConverter;
408 }
409 if (type.getRawType().isInterface()) {
410 return newDefaultReturningProxy(type);
411 }
412 return null;
413 }
414
415 private <F, T> Converter<F, T> defaultConverter(
416 final TypeToken<F> convertFromType, final TypeToken<T> convertToType) {
417 return new Converter<F, T>() {
418 @Override protected T doForward(F a) {
419 return doConvert(convertToType);
420 }
421 @Override protected F doBackward(T b) {
422 return doConvert(convertFromType);
423 }
424
425 private <S> S doConvert(TypeToken<S> type) {
426 return checkNotNull(getDefaultValue(type));
427 }
428 };
429 }
430
431 private static TypeToken<?> getFirstTypeParameter(Type type) {
432 if (type instanceof ParameterizedType) {
433 return TypeToken.of(
434 ((ParameterizedType) type).getActualTypeArguments()[0]);
435 } else {
436 return TypeToken.of(Object.class);
437 }
438 }
439
440 private <T> T newDefaultReturningProxy(final TypeToken<T> type) {
441 return new DummyProxy() {
442 @Override <R> R dummyReturnValue(TypeToken<R> returnType) {
443 return getDefaultValue(returnType);
444 }
445 }.newProxy(type);
446 }
447
448 private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) {
449 if (instance == null) {
450 return Invokable.from(method);
451 } else {
452 return TypeToken.of(instance.getClass()).method(method);
453 }
454 }
455
456 static boolean isPrimitiveOrNullable(Parameter param) {
457 return param.getType().getRawType().isPrimitive() || isNullable(param);
458 }
459
460 private static boolean isNullable(Parameter param) {
461 return param.isAnnotationPresent(Nullable.class);
462 }
463
464 private boolean isIgnored(Member member) {
465 return member.isSynthetic() || ignoredMembers.contains(member);
466 }
467
468
469
470
471 private enum ExceptionTypePolicy {
472
473
474
475
476
477 NPE_OR_UOE() {
478 @Override
479 public boolean isExpectedType(Throwable cause) {
480 return cause instanceof NullPointerException
481 || cause instanceof UnsupportedOperationException;
482 }
483 },
484
485
486
487
488
489
490 NPE_IAE_OR_UOE() {
491 @Override
492 public boolean isExpectedType(Throwable cause) {
493 return cause instanceof NullPointerException
494 || cause instanceof IllegalArgumentException
495 || cause instanceof UnsupportedOperationException;
496 }
497 };
498
499 public abstract boolean isExpectedType(Throwable cause);
500 }
501 }